using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using Microsoft.Samples.BizTalk.Adapter.Tcp;

namespace TcpServer
{

    /// <summary>
    ///     This class represents the main application form.
    /// </summary>
    public partial class Main : Form
    {
        #region Private Instance Members

        private bool            _isListening;
        private Socket          _listenSocket;

        #endregion

        #region Private Instance Delegates

        // This delegate enables asynchronous calls for setting the text property of a control
        private delegate void SetTextCallback(string text, bool append);

        // This delegate enables asynchronous calls to get the value of a control
        private delegate object GetControlValueCallback(Control control);

        // This delegate enables asynchronous calls to set the state of the control
        private delegate void SetControlStateCallback(Control control, bool enabled);

        #endregion

        #region Constructor

        /// <summary>
        ///     This is the default constructor for the class.
        /// </summary>
        public Main()
        {
            InitializeComponent();

            // Initialize members
            this._isListening = false;
            this._listenSocket = null;
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method sets the enabled state of the buttons depending on whether it is listening.
        /// </summary>
        private void SetButtonState()
        {
            // Set button state
            this.SetControlState(this.buttonListen, !this._isListening);
            this.SetControlState(this.buttonStopListening, this._isListening);
        }

        /// <summary>
        ///     This method sets the state of the control in a thread safe manner.
        /// </summary>
        private void SetControlState(Control control, bool enabled)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (control.InvokeRequired)
            {
                // On a different thread
                SetControlStateCallback callback = new SetControlStateCallback(this.SetControlState);
                this.Invoke(callback, control, enabled);
            }
            else
            {
                // On the same thread
                control.Enabled = enabled;
            }
        }

        /// <summary>
        ///     This method gets the control value in a thread safe manner.
        /// </summary>
        private object GetControlValue(Control control)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (control.InvokeRequired)
            {
                // On a different thread
                GetControlValueCallback callback = new GetControlValueCallback(this.GetControlValue);
                return (this.Invoke(callback, control));
            }
            else
            {
                // On the same thread
                if (control is ComboBox)
                {
                    return (((ComboBox)control).SelectedItem);
                }
                if (control is TextBox)
                {
                    return (((TextBox)control).Text);
                }
                if (control is CheckBox)
                {
                    return (((CheckBox)control).Checked);
                }
            }

            // Not a type we support
            return (null);
        }

        /// <summary>
        ///     This method sets the text of the control in a thread safe manner.
        /// </summary>
        private void SetReceiveDataText(string text, bool append)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (this.textReceiveData.InvokeRequired)
            {
                // On a different thread, we must invoke
                SetTextCallback callback = new SetTextCallback(this.SetReceiveDataText);
                this.Invoke(callback, text, append);
            }
            else
            {
                // On the same thread, we can just set it
                if (append)
                {
                    this.textReceiveData.Text += text;
                }
                else
                {
                    this.textReceiveData.Text = text;
                }
            }
        }

        /// <summary>
        ///     This method sets the text of the control in a thread safe manner.
        /// </summary>
        private void SetStatusBarText(string text, bool append)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (this.toolStripContainer.InvokeRequired)
            {
                // On a different thread, we must invoke
                SetTextCallback callback = new SetTextCallback(this.SetStatusBarText);
                this.Invoke(callback, text, append);
            }
            else
            {
                // On the same thread, we can just set it
                this.labelStatus.Text = text;
            }
        }

        /// <summary>
        ///     This method returns a hex string in the form NN NN NN from the byte array.
        /// </summary>
        private string GetHexString(byte[] bytes, int offset, int count)
        {
            StringBuilder sb = new StringBuilder(count * 2);
            for (int i = offset; i < count; i++)
            {
                sb.AppendFormat("{0:X2} ", bytes[i]);
            }
            return (sb.ToString(0, sb.Length - 1));
        }

        /// <summary>
        ///     This method returns byte buffer as an appropriately encoded string.
        /// </summary>
        private string GetDecodedData(byte[] data, int offset, int count, DataEncodingTypes encoding, string customValue, bool bigEndian, bool handleByteOrderMark)
        {
            string decodedData = string.Empty;
            switch (encoding)
            {
                case DataEncodingTypes.ASCII:

                    decodedData = Encoding.ASCII.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.Binary:

                    decodedData = this.GetHexString(data, offset, count);
                    break;

                case DataEncodingTypes.CustomIdentifier:

                    decodedData = Encoding.GetEncoding(int.Parse(customValue)).GetString(data, offset, count);
                    break;

                case DataEncodingTypes.CustomName:

                    decodedData = Encoding.GetEncoding(customValue).GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF16:

                    Encoding utf16Encoding = new UnicodeEncoding(bigEndian, handleByteOrderMark, true);
                    decodedData = utf16Encoding.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF32:

                    Encoding utf32Encoding = new UTF32Encoding(bigEndian, handleByteOrderMark, true);
                    decodedData = utf32Encoding.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF7:

                    decodedData = Encoding.UTF7.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF8:

                    Encoding utf8Encoding = new UTF8Encoding(handleByteOrderMark, true);
                    decodedData = utf8Encoding.GetString(data, offset, count);
                    break;
            }

            // Returns the decoded data
            return (decodedData);
        }

        /// <summary>
        ///     This method returns the string converted into the appropriate encoding.
        /// </summary>
        private byte[] GetEncodedData(string data, DataEncodingTypes encoding, string customValue, bool bigEndian, bool emitByteOrderMark)
        {
            byte[] encodedBytes = null;
            switch (encoding)
            {
                case DataEncodingTypes.ASCII :

                    encodedBytes = Encoding.ASCII.GetBytes(data);
                    break;

                case DataEncodingTypes.Binary :

                    string[] bytes = data.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    encodedBytes = new byte[bytes.Length];
                    for (int i = 0; i < encodedBytes.Length; i++)
                    {
                        encodedBytes[i] = Convert.ToByte(bytes[i], 16);
                    }
                    break;

                case DataEncodingTypes.CustomIdentifier :

                    encodedBytes = Encoding.GetEncoding(int.Parse(customValue)).GetBytes(data);
                    break;

                case DataEncodingTypes.CustomName :

                    encodedBytes = Encoding.GetEncoding(customValue).GetBytes(data);
                    break;

                case DataEncodingTypes.UTF16 :

                    Encoding utf16Encoding = new UnicodeEncoding(bigEndian, emitByteOrderMark, true);
                    encodedBytes = utf16Encoding.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF32 :

                    Encoding utf32Encoding = new UTF32Encoding(bigEndian, emitByteOrderMark, true);
                    encodedBytes = utf32Encoding.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF7 :

                    encodedBytes = Encoding.UTF7.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF8 :

                    Encoding utf8Encoding = new UTF8Encoding(emitByteOrderMark, true);
                    encodedBytes = utf8Encoding.GetBytes(data);
                    break;
            }

            // Returns the encoded bytes
            return (encodedBytes);
        }

        #endregion

        #region Private Event Handlers

        /// <summary>
        ///     This event handler is called when the form is loaded.
        /// </summary>
        private void Main_Load(object sender, EventArgs e)
        {
            // Default some of the values
            this.textListenPort.Text = "1970";
            this.labelResponse.Enabled = false;
            this.textResponse.Enabled = false;
            this.labelReceiveFrameStart.Enabled = false;
            this.labelReceiveFrameEnd.Enabled = false;
            this.textReceiveFrameStart.Enabled = false;
            this.textReceiveFrameEnd.Enabled = false;
            this.checkReceiveBinaryFrame.Enabled = false;
            this.SetButtonState();

            // Load encodings
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.ASCII);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomIdentifier);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomName);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF16);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF32);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF7);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF8);
            this.comboReceiveEncoding.SelectedIndex = 0;
        }

        /// <summary>
        ///     This event handler is called whenever the start listening button is clicked.
        /// </summary>
        private void buttonListen_Click(object sender, EventArgs e)
        {
            try
            {
                // Validate
                if (this.textListenPort.Text == string.Empty) throw new ArgumentNullException("Enter the port to listen on for incoming connections");
                if (this.checkAutoReply.Checked)
                {
                    if (this.textResponse.Text == string.Empty) throw new ArgumentNullException("Enter the response data when auto replying");
                    if (this.textReceiveFrameStart.Text == string.Empty) throw new ArgumentNullException("Enter the start frame delimiter for the response");
                    if (this.textReceiveFrameEnd.Text == string.Empty) throw new ArgumentNullException("Enter the end frame delimiter for the response");
                }

                // Get port
                int port = int.Parse(this.textListenPort.Text);

                // Create socket, bind and then listen
                this._listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this._listenSocket.Bind(new IPEndPoint(IPAddress.Any, port));
                this._listenSocket.Listen(5);

                // Begin accept
                this._listenSocket.BeginAccept(new AsyncCallback(this.AcceptCallback), null);

                this._isListening = true;
                this.SetButtonState();
                this.labelStatus.Text = "Listening";
            }
            catch (Exception ex)
            {
                this._isListening = false;
                this.SetButtonState();

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        ///     This event handler is called whenever the stop listening button is clicked.
        /// </summary>
        private void buttonStopListening_Click(object sender, EventArgs e)
        {
            try
            {
                // Stop listener
                this._listenSocket.Close();

                this._isListening = false;
                this.SetButtonState();
                this.labelStatus.Text = "Stopped listening";
            }
            catch (Exception ex)
            {
                this._isListening = false;
                this.SetButtonState();

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        ///     This event handler is called whenever the check box state is changed.
        /// </summary>
        private void checkAutoReply_CheckedChanged(object sender, EventArgs e)
        {
            // Set the state of the response text box
            this.SetControlState(this.labelResponse, this.checkAutoReply.Checked);
            this.SetControlState(this.textResponse, this.checkAutoReply.Checked);
            this.SetControlState(this.labelReceiveFrameStart, this.checkAutoReply.Checked);
            this.SetControlState(this.labelReceiveFrameEnd, this.checkAutoReply.Checked);
            this.SetControlState(this.textReceiveFrameStart, this.checkAutoReply.Checked);
            this.SetControlState(this.textReceiveFrameEnd, this.checkAutoReply.Checked);
            this.SetControlState(this.checkReceiveBinaryFrame, this.checkAutoReply.Checked);
        }

        /// <summary>
        ///     This event handler is called whenever the index is changed in the combo box.
        /// </summary>
        private void comboReceiveEncoding_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Check encoding
            DataEncodingTypes encoding = (DataEncodingTypes)this.comboReceiveEncoding.SelectedItem;
            if (encoding == DataEncodingTypes.UTF8 || encoding == DataEncodingTypes.UTF16 || encoding == DataEncodingTypes.UTF32)
            {
                this.labelReceiveCustomEncoding.Enabled = false;
                this.textReceiveCustomEncoding.Enabled = false;
                this.checkReceiveEmitByteOrderMarker.Enabled = true;
                if (encoding != DataEncodingTypes.UTF8)
                {
                    this.checkReceiveByteOrder.Enabled = true;
                }
                else
                {
                    this.checkReceiveByteOrder.Enabled = false;
                }
            }
            else if (encoding == DataEncodingTypes.CustomIdentifier || encoding == DataEncodingTypes.CustomName)
            {
                this.labelReceiveCustomEncoding.Enabled = true;
                this.textReceiveCustomEncoding.Enabled = true;
                this.checkReceiveEmitByteOrderMarker.Enabled = false;
                this.checkReceiveByteOrder.Enabled = false;
            }
            else
            {
                this.labelReceiveCustomEncoding.Enabled = false;
                this.textReceiveCustomEncoding.Enabled = false;
                this.checkReceiveEmitByteOrderMarker.Enabled = false;
                this.checkReceiveByteOrder.Enabled = false;
            }
        }

        /// <summary>
        ///     This asynchronous handler is called whenever a connection has been made and a socket is waiting.
        /// </summary>
        private void AcceptCallback(IAsyncResult asyncResult)
        {
            try
            {
                // Got a connection, accept it
                Socket clientSocket = this._listenSocket.EndAccept(asyncResult);

                // Receive request
                this.ReceiveRequest(clientSocket);

                // Send response
                if (this.checkAutoReply.Checked)
                {
                    this.SendResponse(clientSocket);
                }

                // Shut socket
                clientSocket.Shutdown(SocketShutdown.Receive);
                clientSocket.Close();

                // Accept again
                this._listenSocket.BeginAccept(new AsyncCallback(this.AcceptCallback), null);
            }
            catch (ObjectDisposedException)
            {
                // Socket closed while this delegate is still running
            }
            catch (Exception ex)
            {
                this._listenSocket.Close();
                this._isListening = false;
                this.SetButtonState();

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method receives a request from the socket.
        /// </summary>
        private void ReceiveRequest(Socket clientSocket)
        {
            // Set socket options: receive timeout, send timeout, receive buffer size, send buffer size
            clientSocket.ReceiveBufferSize = 8192;
            clientSocket.ReceiveTimeout = 500;
            clientSocket.SendBufferSize = 8192;
            clientSocket.SendTimeout = 500;

            // Set up variables for receiving data
            int bytesRead = 0;
            int totalBytesRead = 0;
            byte[] buffer = new byte[2048];

            using (MemoryStream stream = new MemoryStream())
            {
                // Receive data
                try
                {
                    while ((bytesRead = clientSocket.Receive(buffer)) > 0)
                    {
                        stream.Write(buffer, 0, bytesRead);
                        totalBytesRead += bytesRead;
                        this.SetStatusBarText("Received " + totalBytesRead.ToString() + " total bytes from socket", false);
                    }
                }
                catch (SocketException e)
                {
                    System.Diagnostics.Debug.WriteLine("Socket Error: " + e.SocketErrorCode.ToString());

                    // If we time out then it's ok to carry on
                    if (!(e.SocketErrorCode == SocketError.TimedOut || e.SocketErrorCode == SocketError.WouldBlock))
                    {
                        // Some other socket exception that must be thrown
                        throw (e);
                    }
                }

                // Get data in a byte array
                stream.Position = 0;
                buffer = new byte[stream.Length];
                stream.Read(buffer, 0, (int)stream.Length);
                stream.Close();
            }

            // Get decoded data and set textbox value
            string data = this.GetDecodedData(buffer, 0, buffer.Length, (DataEncodingTypes)this.GetControlValue(this.comboReceiveEncoding), (string)this.GetControlValue(this.textReceiveCustomEncoding), (bool)this.GetControlValue(this.checkReceiveByteOrder), (bool)this.GetControlValue(this.checkReceiveEmitByteOrderMarker));
            this.SetReceiveDataText(data, true);
        }

        /// <summary>
        ///     This method sends a response on the socket.
        /// </summary>
        private void SendResponse(Socket clientSocket)
        {
            // Get frame start and end bytes
            DataEncodingTypes frameStartEncoding = (DataEncodingTypes)this.GetControlValue(this.comboReceiveEncoding);
            if ((bool)this.GetControlValue(this.checkReceiveBinaryFrame))
            {
                frameStartEncoding = DataEncodingTypes.Binary;
            }
            byte[] frameStart = this.GetEncodedData((string)this.GetControlValue(this.textReceiveFrameStart), frameStartEncoding, (string)this.GetControlValue(this.textReceiveCustomEncoding), (bool)this.GetControlValue(this.checkReceiveByteOrder), (bool)this.GetControlValue(this.checkReceiveEmitByteOrderMarker));

            DataEncodingTypes frameEndEncoding = (DataEncodingTypes)this.GetControlValue(this.comboReceiveEncoding);
            if ((bool)this.GetControlValue(this.checkReceiveBinaryFrame))
            {
                frameEndEncoding = DataEncodingTypes.Binary;
            }
            byte[] frameEnd = this.GetEncodedData((string)this.GetControlValue(this.textReceiveFrameEnd), frameEndEncoding, (string)this.GetControlValue(this.textReceiveCustomEncoding), (bool)this.GetControlValue(this.checkReceiveByteOrder), (bool)this.GetControlValue(this.checkReceiveEmitByteOrderMarker));

            // Get data bytes
            byte[] responseData = this.GetEncodedData((string)this.GetControlValue(this.textResponse), (DataEncodingTypes)this.GetControlValue(this.comboReceiveEncoding), (string)this.GetControlValue(this.textReceiveCustomEncoding), (bool)this.GetControlValue(this.checkReceiveByteOrder), (bool)this.GetControlValue(this.checkReceiveEmitByteOrderMarker));

            // Get response bytes
            byte[] responseBuffer = new byte[frameStart.Length + responseData.Length + frameEnd.Length];
            frameStart.CopyTo(responseBuffer, 0);
            responseData.CopyTo(responseBuffer, frameStart.Length);
            frameEnd.CopyTo(responseBuffer, frameStart.Length + responseData.Length);

            // Send data
            int bytesSent = clientSocket.Send(responseBuffer);
            this.SetStatusBarText("Sent " + bytesSent.ToString() + " bytes as response", false);
        }

        #endregion
    }
}